/*****************************************************************************/
/**
 *  @file   MainView.cpp
 *  @author Naohisa Sakamoto
 */
/*----------------------------------------------------------------------------
 *
 *  Copyright (c) Visualization Laboratory, Kyoto University.
 *  All rights reserved.
 *  See http://www.viz.media.kyoto-u.ac.jp/kvs/copyright/ for details.
 *
 *  $Id: MainView.cpp 88 2013-12-27 14:49:59Z naohisa.sakamoto $
 */
/*****************************************************************************/
#include "MainView.h"
#include "OverView.h"
#include "DataSet.h"
#include "ResourceMonitor.h"
#include "Frustum.h"
#include "ResourceLabelWidget.h"
#include "OverViewUpdator.h"
#include "Util.h"
#include <kvs/glut/Application>
#include <kvs/glut/Screen>
#include <kvs/RGBColor>
#include <kvs/ColorMap>
#include <kvs/TransferFunction>
#include <kvs/ObjectManager>
#include <kvs/RendererManager>
#include <kvs/ParticleBasedRenderer>
#include <kvs/StochasticUniformGridRenderer>
#include <kvs/StochasticRenderingCompositor>
#include "StochasticPartialUniformGridRenderer.h"

#include <kvs/InitializeEventListener>
class InitializeEvent : public kvs::InitializeEventListener
{
    MainView* m_view;

public:

    InitializeEvent( MainView* view ): m_view( view ) {}

    void update()
    {
        m_view->stopTimer();
    }
};

#include <kvs/KeyPressEventListener>
class KeyPressEvent : public kvs::KeyPressEventListener
{
    MainView* m_view;

public:

    KeyPressEvent( MainView* view ): m_view( view ) {}

    void update( kvs::KeyEvent* event )
    {
        switch ( event->key() )
        {
        case kvs::Key::Space: { m_view->toggleTimer(); break; }
        case kvs::Key::i: { m_view->incrementTimeStep(); break; }
        case kvs::Key::d: { m_view->decrementTimeStep(); break; }
        case kvs::Key::o: { m_view->changeRenderer(); break; }
        case kvs::Key::c: { m_view->cullVolume(); break; }
        default: break;
        }
    }
};

#include <kvs/PaintEventListener>
class PaintEvent : public kvs::PaintEventListener
{
    MainView* m_view;

public:

    PaintEvent( MainView* view ): m_view( view ) {}

    void update()
    {
        m_view->adaptiveSwitching();
    }
};

#include <kvs/TimerEventListener>
class TimerEvent : public kvs::TimerEventListener
{
    MainView* m_view;

public:

    TimerEvent( MainView* view ): m_view( view ) {}

    void update( kvs::TimeEvent* event )
    {
        m_view->incrementTimeStep( true );
        m_view->redraw();
    }
};

#include <kvs/MousePressEventListener>
class MousePressEvent : public kvs::MousePressEventListener
{
    MainView* m_view;

public:

    MousePressEvent( MainView* view ): m_view( view ) {}

    void update( kvs::MouseEvent* event )
    {
        m_view->stopTimer();
        m_view->redraw();
    }
};

template <typename DataType>
kvs::StructuredVolumeObject* Crop(
    const kvs::StructuredVolumeObject* volume,
    const kvs::Vec3& min_range,
    const kvs::Vec3& max_range )
{
    const size_t imin = static_cast<size_t>( kvs::Math::Max( min_range.x(), 0.0f ) );
    const size_t jmin = static_cast<size_t>( kvs::Math::Max( min_range.y(), 0.0f ) );
    const size_t kmin = static_cast<size_t>( kvs::Math::Max( min_range.z(), 0.0f ) );

    const size_t imax = static_cast<size_t>( kvs::Math::Min( max_range.x(), volume->resolution().x() - 1.0f ) );
    const size_t jmax = static_cast<size_t>( kvs::Math::Min( max_range.y(), volume->resolution().y() - 1.0f ) );
    const size_t kmax = static_cast<size_t>( kvs::Math::Min( max_range.z(), volume->resolution().z() - 1.0f ) );

    const size_t dimx = imax - imin + 1;
    const size_t dimy = jmax - jmin + 1;
    const size_t dimz = kmax - kmin + 1;

    const size_t veclen = volume->veclen();
    const size_t line_size = volume->numberOfNodesPerLine();
    const size_t slice_size = volume->numberOfNodesPerSlice();
    const DataType* src = static_cast<const DataType*>( volume->values().data() );

    kvs::ValueArray<DataType> values( dimx * dimy * dimz * veclen );
    DataType* dst = values.data();
    for ( size_t k = kmin; k <= kmax; k++ )
    {
        for ( size_t j = jmin; j <= jmax; j++ )
        {
            for ( size_t i = imin; i <= imax; i++ )
            {
                const size_t index = ( i + j * line_size + k * slice_size ) * veclen;
                for ( size_t v = 0; v < veclen; v++ )
                {
                    *(dst++) = src[ index + v ];
                }
            }
        }
    }

    kvs::StructuredVolumeObject* output = new kvs::StructuredVolumeObject();
    output->setGridType( volume->gridType() );
    output->setResolution( kvs::Vector3ui( dimx, dimy, dimz ) );
    output->setVeclen( volume->veclen() );
    output->setValues( kvs::AnyValueArray( values ) );
//    output->updateMinMaxValues();
//    output->updateMinMaxCoords();
    output->setMinMaxValues( volume->minValue(), volume->maxValue() );
//    output->setMinMaxObjectCoords( kvs::Vec3::Zero(), kvs::Vec3( dimx - 1, dimy - 1, dimz - 1 ) );
//    output->setMinMaxExternalCoords( min_range, max_range );
//    output->setMinMaxObjectCoords( min_range, max_range );
//    output->setMinMaxObjectCoords( volume->minExternalCoord(), volume->maxExternalCoord() );
    output->setMinMaxObjectCoords( volume->minObjectCoord(), volume->maxObjectCoord() );
    output->setMinMaxExternalCoords( volume->minExternalCoord(), volume->maxExternalCoord() );

//    output->multiplyXform( kvs::Xform::Translation( min_range ) );

    return output;
}

MainView::MainView( kvs::glut::Application* app ):
    kvs::glut::Screen( app ),
    m_over_view( 0 ),
    m_data_set( 0 ),
    m_resource_monitor( 0 ),
    m_resource_label_widget( 0 ),
    m_over_view_updator( 0 ),
    m_stored_particle_data_size( 0 ),
    m_stored_volume_data_size( 0 )
{
    setTitle( "Main view" );
    setBackgroundColor( kvs::RGBColor::White() );

    int repeats = 20;
    m_compositor = new kvs::StochasticRenderingCompositor( this->scene() );
    m_compositor->setRepetitionLevel( repeats );
    m_compositor->enableLODControl();

    setEvent( m_compositor );
    addEvent( new InitializeEvent( this ) );
    addEvent( new KeyPressEvent( this ) );
    addEvent( new MousePressEvent( this ) );
    addEvent( new PaintEvent( this ) );

    m_resource_label_widget = new ResourceLabelWidget( this ),
    m_resource_label_widget->show();

    m_timer.setInterval( 10 );

    addTimerEvent( new TimerEvent( this ), &m_timer );
}

MainView::~MainView()
{
    if ( m_resource_label_widget ) delete m_resource_label_widget;
    if ( m_over_view_updator ) delete m_over_view_updator;
    if ( m_compositor ) delete m_compositor;
}

void MainView::attachOverViewScreen( OverView* over_view )
{
    m_over_view = over_view;
    m_over_view_updator = new OverViewUpdator( m_over_view );
    addEvent( m_over_view_updator );
}

void MainView::attachDataSet( DataSet* data_set )
{
    m_data_set = data_set;
    m_resource_label_widget->attachDataSet( data_set );
    m_over_view_updator->attachDataSet( m_data_set );
}

void MainView::attachResourceMonitor( ResourceMonitor* resource_monitor )
{
    m_resource_monitor = resource_monitor;
    m_resource_label_widget->attachResourceMonitor( resource_monitor );
}

void MainView::loadObject()
{
    if ( scene()->objectManager()->object( m_data_set->particle()->name() ) )
    {
        scene()->replaceObject( m_data_set->particle()->name(), m_data_set->particle().get(), false );
    }
    else
    {
        kvs::glsl::ParticleBasedRenderer* renderer = new kvs::glsl::ParticleBasedRenderer();
        renderer->setName( "OPBR" );
        scene()->registerObject( m_data_set->particle().get(), renderer );

        m_initial_modelview = renderer->initialModelViewMatrix();
        m_initial_projection = renderer->initialProjectionMatrix();
        m_initial_viewport = renderer->initialViewport();

        m_stored_particle_data_size = m_data_set->particleDataSize();
        m_stored_volume_data_size = 0;
    }
}

void MainView::incrementTimeStep( const bool loop )
{
    m_data_set->incrementTimeStep( loop );
    this->loadObject();
}

void MainView::decrementTimeStep( const bool loop )
{
    m_data_set->decrementTimeStep( loop );
    this->loadObject();
}

void MainView::startTimer()
{
    m_timer.start();
}

void MainView::stopTimer()
{
    m_timer.stop();
}

void MainView::toggleTimer()
{
    if ( this->isTimerStopped() ) this->startTimer();
    else this->stopTimer();
}

bool MainView::isTimerStopped() const
{
    return m_timer.isStopped();
}

void MainView::adaptiveSwitching()
{
    if ( this->has_OPBR() )
    {
        std::cout << "OPBR" << std::endl;
        if ( this->can_IPBR() )
        {
            this->switch_to_IPBR();
            m_stored_particle_data_size = 0;
            m_stored_volume_data_size = m_data_set->clippedVolumeDataSize();
        }
    }
    else if ( this->has_IPBR() )
    {
        std::cout << "IPBR" << std::endl;
        if ( this->can_OPBR() )
        {
            this->switch_to_OPBR();
            m_stored_particle_data_size = m_data_set->particleDataSize();
            m_stored_volume_data_size = 0;
        }
    }
}

void MainView::changeRenderer()
{
    if ( scene()->rendererManager()->renderer("OPBR") )
    {
        typedef kvs::glsl::ParticleBasedRenderer OPBR;
        const OPBR* opbr = static_cast<OPBR*>( scene()->rendererManager()->renderer("OPBR") );
        m_initial_modelview = opbr->initialModelViewMatrix();
        m_initial_projection = opbr->initialProjectionMatrix();
        m_initial_viewport = opbr->initialViewport();

        kvs::ColorMap cmap( 256 );
        cmap.addPoint( 0, kvs::RGBColor::Red() );
        cmap.addPoint( 255, kvs::RGBColor::Red() );
        cmap.create();

        kvs::TransferFunction tfunc( cmap );
        kvs::StochasticUniformGridRenderer* renderer = new kvs::StochasticUniformGridRenderer();
        renderer->setName( "IPBR" );
        renderer->setTransferFunction( tfunc );

        scene()->replaceObject( m_data_set->particle().get()->name(), m_data_set->volume().get(), false );
        scene()->replaceRenderer( "OPBR", renderer, true );
    }
    else
    {
        size_t repeats = 5;
        const kvs::Mat4& m = m_initial_modelview;
        const kvs::Mat4& p = m_initial_projection;
        const kvs::Vec4& v = m_initial_viewport;
        kvs::glsl::ParticleBasedRenderer* renderer = new kvs::glsl::ParticleBasedRenderer( m, p, v );
        renderer->setName( "OPBR" );
        renderer->setRepetitionLevel( repeats );

//        scene()->replaceObject( m_data_set->volume().get()->name(), m_data_set->particle().get(), false );
        scene()->replaceObject( "CroppedVolume", m_data_set->particle().get(), false );
        scene()->replaceRenderer( "IPBR", renderer, true );
    }
}

void MainView::cullVolume()
{
    if ( scene()->rendererManager()->renderer("OPBR") )
    {
        typedef kvs::glsl::ParticleBasedRenderer OPBR;
        const OPBR* opbr = static_cast<OPBR*>( scene()->rendererManager()->renderer("OPBR") );
        m_initial_modelview = opbr->initialModelViewMatrix();
        m_initial_projection = opbr->initialProjectionMatrix();
        m_initial_viewport = opbr->initialViewport();

        kvs::ColorMap cmap( 256 );
        cmap.addPoint( 0, kvs::RGBColor::Red() );
        cmap.addPoint( 255, kvs::RGBColor::Red() );
        cmap.create();

        kvs::StructuredVolumeObject* volume = Crop<kvs::UInt8>(
            m_data_set->volume().get(),
            m_data_set->range().min,
            m_data_set->range().max );
        volume->setName( "CroppedVolume" );
//        volume->setMinMaxExternalCoords( m_data_set->range().min, m_data_set->range().max );
//        volume->setMinMaxObjectCoords( m_data_set->range().min, m_data_set->range().max );
//        volume->setMinMaxExternalCoords( m_data_set->volume()->minExternalCoord(), m_data_set->volume()->maxExternalCoord() );
//        volume->multiplyXform( kvs::Xform::Translation( m_data_set->range().min ) );

//        std::cout << "ORIGINAL VOLUME" << std::endl;
//        std::cout << "    min-obj: " << m_data_set->volume()->minObjectCoord() << std::endl;
//        std::cout << "    max-obj: " << m_data_set->volume()->maxObjectCoord() << std::endl;

//        size_t repeats = 1;
        kvs::TransferFunction tfunc( cmap );
        kvs::StochasticUniformGridRenderer* renderer = new kvs::StochasticUniformGridRenderer();
        renderer->setName( "IPBR" );
//        renderer->setRepetitionLevel( repeats );
        renderer->setTransferFunction( tfunc );

        std::cout << "BEFORE" << std::endl;
        std::cout << scene()->objectManager()->objectCenter() << std::endl;
        std::cout << scene()->objectManager()->normalize() << std::endl;
        std::cout << scene()->objectManager()->minObjectCoord() << std::endl;
        std::cout << scene()->objectManager()->maxObjectCoord() << std::endl;

//        kvs::Vec3 min = scene()->objectManager()->minObjectCoord();
//        kvs::Vec3 max = scene()->objectManager()->maxObjectCoord();

        scene()->replaceObject( m_data_set->particle()->name(), volume, false );
        scene()->replaceRenderer( "OPBR", renderer, true );

//        scene()->objectManager()->setMinMaxObjectCoords( min, max );

        std::cout << "AFTER" << std::endl;
        std::cout << scene()->objectManager()->objectCenter() << std::endl;
        std::cout << scene()->objectManager()->normalize() << std::endl;
        std::cout << scene()->objectManager()->minObjectCoord() << std::endl;
        std::cout << scene()->objectManager()->maxObjectCoord() << std::endl;
    }
    else
    {
        kvs::StructuredVolumeObject* volume = Crop<kvs::UInt8>(
            m_data_set->volume().get(),
            m_data_set->range().min,
            m_data_set->range().max );
        volume->setName( "CroppedVolume" );
        volume->setMinMaxExternalCoords( m_data_set->range().min, m_data_set->range().max );

        scene()->replaceObject( "CroppedVolume", volume, true );
    }
}

bool MainView::has_OPBR()
{
//    return scene()->rendererManager()->renderer("OPBR") != NULL;
    return m_stored_particle_data_size > 0;
}

bool MainView::has_IPBR()
{
//    return scene()->rendererManager()->renderer("IPBR") != NULL;
    return m_stored_volume_data_size > 0;
}

bool MainView::can_OPBR()
{
    KVS_ASSERT( this->has_ipbr() );
    if ( m_data_set->range().max == m_data_set->range().min ) return false;

    const size_t V = m_stored_volume_data_size;
    const size_t Vc = m_data_set->clippedVolumeDataSize();

    return V < Vc;
}

bool MainView::can_IPBR()
{
    KVS_ASSERT( this->has_opbr() );
    if ( m_data_set->range().max == m_data_set->range().min ) return false;

    const size_t P = m_stored_particle_data_size;
    const size_t Mf = m_resource_monitor->freeGPUMemory();
    const size_t M = Mf + P;
    const size_t Vc = m_data_set->clippedVolumeDataSize();
    return M > Vc;
}

void MainView::switch_to_OPBR()
{
    KVS_ASSERT( this->has_ipbr() );

    std::cout << "\tswitch to OPBR" << std::endl;

    const kvs::Mat4& m = m_initial_modelview;
    const kvs::Mat4& p = m_initial_projection;
    const kvs::Vec4& v = m_initial_viewport;

    typedef kvs::glsl::ParticleBasedRenderer OPBR;
    OPBR* renderer = new OPBR( m, p, v );
    renderer->setName( "OPBR" );

    scene()->replaceObject( m_data_set->volume()->name(), m_data_set->particle().get(), false );
    scene()->replaceRenderer( "IPBR", renderer, true );
}

void MainView::switch_to_IPBR()
{
    KVS_ASSERT( this->has_opbr() );

    std::cout << "\tswitch to IPBR" << std::endl;

    typedef kvs::glsl::ParticleBasedRenderer OPBR;
    typedef StochasticPartialUniformGridRenderer IPBR;

    const OPBR* opbr = OPBR::DownCast( scene()->rendererManager()->renderer("OPBR") );
    m_initial_modelview = opbr->initialModelViewMatrix();
    m_initial_projection = opbr->initialProjectionMatrix();
    m_initial_viewport = opbr->initialViewport();

    kvs::ColorMap cmap( 256 );
    cmap.addPoint( 0, kvs::RGBColor::Red() );
    cmap.addPoint( 255, kvs::RGBColor::Red() );
    cmap.create();

    IPBR* renderer = new IPBR();
    renderer->setName( "IPBR" );
    renderer->setTransferFunction( kvs::TransferFunction( cmap ) );
    renderer->setOffset( kvs::Vec3i( m_data_set->range().min ) );
    renderer->setDimension( kvs::Vec3ui( m_data_set->range().max - m_data_set->range().min + kvs::Vec3::All(1) ) );

    std::cout << "   resolution = " << m_data_set->volume()->resolution() << std::endl;
    std::cout << "   offset = " << kvs::Vec3i( m_data_set->range().min ) << std::endl;
    std::cout << "   dimension = " << kvs::Vec3ui( m_data_set->range().max - m_data_set->range().min + kvs::Vec3::All(1) ) << std::endl;
    std::cout << "   data size = " << Util::DataSizeWithUnit( m_data_set->clippedVolumeDataSize() ) << std::endl;

    scene()->replaceObject( m_data_set->particle()->name(), m_data_set->volume().get(), false );
    scene()->replaceRenderer( "OPBR", renderer, true );
}
